<!DOCTYPE html>
<html lang="zh-CN">
  <head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <meta name="description" content="">
  <meta name="keywords" content="">
  
    <link rel="icon" href="/avatar.jpg">
  
    
  <title>在 spacemacs 生成 micro-state 的同时为每个命令生成对应的执行并进入 micro-state 的函数 | 科学君的不科学博客</title>
  <link rel="stylesheet" href="/style.css">
  <link rel="stylesheet" href="/lib/jquery.fancybox.min.css">
  <link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">
  <script type="text/javascript" src="https://tajs.qq.com/stats?sId=65546304" charset="UTF-8"></script>
  <script>
  (function(){
      var bp = document.createElement('script');
      var curProtocol = window.location.protocol.split(':')[0];
      if (curProtocol === 'https') {
          bp.src = 'https://zz.bdstatic.com/linksubmit/push.js';
      }
      else {
          bp.src = 'http://push.zhanzhang.baidu.com/push.js';
      }
      var s = document.getElementsByTagName("script")[0];
      s.parentNode.insertBefore(bp, s);
  })();
  </script>
</head>

<body>
  <header>
  <div class="header-container">
    <a class='logo' href="/">
      <span>科学君的不科学博客</span>
    </a>
    <ul class="right-header">
      
        <li class="nav-item">
          
            <a href="/" class="item-link">首页</a>
          
        </li>
      
        <li class="nav-item">
          
            <a href="/about" class="item-link">关于</a>
          
        </li>
      
        <li class="nav-item">
          
            <a href="/archives" class="item-link">归档</a>
          
        </li>
      
        <li class="nav-item">
          
            <a href="/tags" class="item-link">标签</a>
          
        </li>
      
        <li class="nav-item">
          
            <a href="/atom.xml" class="item-link">FEED 订阅</a>
          
        </li>
      
    </ul>
  </div>
</header>

  <main id='post'>
  <div class="content">
    <article>
      <section class="content markdown-body">
        <h1>在 spacemacs 生成 micro-state 的同时为每个命令生成对应的执行并进入 micro-state 的函数</h1>
        <div class='post-meta'>
          <i class="fa fa-calendar" aria-hidden="true"></i>
          <time>2016年09月02日</time>
          
          | <i class="fa fa-folder-open-o" aria-hidden="true"></i> 
  <div class="article-category">
    <a class="article-category-link" href="/categories/blog/">blog</a>
  </div>



          
          
          |
          
          <i class="fa fa-tags" aria-hidden="true"></i>
          
          
  <a href="/tags/#emacs" class='tag'>emacs</a>

  <a href="/tags/#spacemacs" class='tag'>spacemacs</a>


          
        </div>
        <ul>
<li><a href="#sec-">需求</a></li>
<li><a href="#sec-">我的实现</a></li>
<li><a href="#sec-">使用</a><ul>
<li><a href="#sec-">另一个宏</a></li>
<li><a href="#sec-">示例 1</a></li>
<li><a href="#sec-">示例 2</a></li>
</ul>
</li>
<li><a href="#sec-">经验</a></li>
</ul>
<h1 id="需求"><a href="#需求" class="headerlink" title="需求"></a>需求<a id="sec-"></a></h1><p>spacemacs 有一个叫做 micro-state 的功能，宏 <code>spacemacs|define-micro-state</code> 可以生成 一个 micro-state 和相应的函数。当调用函数进入 micro-state 的时候，可以暂时应用一个 临时的键绑定。比如定义一个翻页的 micro-state，启动之后可以用 d u f b 来翻页，不需要 按下 Ctrl。如果按下这四个按钮之外的按键，就会退出 micro-state 并执行那个按键原本的 功能。</p>
<p>这个 micro-state 还有另一个功能：可以指定一个在进入 micro-state 的时候求值的表达式。 比如定义 micro-state 为四个翻页方式，设定在进入 micro-state 的同时求值 <code>(evil-scroll-page-down 0)</code> ，然后把激活 micro-state 的函数绑定到 <code>C-f</code> 上。 这样在按键翻页的时候只有第一次需要按 <code>C-f</code> ，之后只需要按 <code>f</code> 来翻页就可以了。</p>
<p>但是这样做只能指定一个动作，如果我想在按下 <code>C-b</code> 的时候向上翻页并进入 micro-state， 这就没有办法了。而且对于刚才的设置，我们是没法使用 <code>2 C-f</code> 这样的操作来向下翻两页的。 在 <a href="https://emacs-china.org/t/ctrl/1039/20?u=liu233w" target="_blank" rel="noopener">emacs-china 的一个帖子</a> 中提供了一个思路，就是定义一个函数，这个函数拥有 interactive 和参数，会先调用原来的函数，然后激活 micro-state。</p>
<h1 id="我的实现"><a href="#我的实现" class="headerlink" title="我的实现"></a>我的实现<a id="sec-"></a></h1><p>我定义了一个宏，可以自动完成上面所说的操作。代码在： <a href="https://github.com/Liu233w/.spacemacs.d/blob/master/libs/multiple-micro-state.el" target="_blank" rel="noopener">https://github.com/Liu233w/.spacemacs.d/blob/master/libs/multiple-micro-state.el</a></p>
<p>宏的名字是 <code>mms|define-multiple-micro-state</code> ，使用方式与 <code>spacemacs|define-micro-state</code> 相同。注意这个只能在 spacemacs 中使用，因为它使用了宏 <code>spacemacs|define-micro-state</code> 。</p>
<p>在执行之后，会按照 <code>spacemacs|define-micro-state</code> 的命名方式生成一个 micro-state， 并且给它定义了一个别名 <code>名称:micro-state</code> ，并且给定义中的每一个函数生成一个名为 <code>名称:函数名-then-enter-micro-state</code> 的函数，这个函数不接受参数，有一个不接受参数的 interactive 形式。调用之后会使用 <code>command-execute</code> 来执行原来的函数。</p>
<p>同时，如果有一些使用 <code>evil-define-command</code> 定义的没法获取 interactive 命令的函数的话， 这个函数会自动加上一个 interactive 命令。</p>
<p>我的宏还支持一个额外的功能：自动生成文档。</p>
<p>如果参数 <code>:doc</code> 的值是 auto 的话（不需要加单引号），宏将根据按键和调用的函数名自动 生成文档，然后设置 <code>:use-minibuffer t</code> 的话，就可以把按键的提示放进 mini-buffer 了。</p>
<p>另外，这个宏还有一个额外的选项 <code>:with-full-arguments</code> ，默认为 nil，如果设置为 t， 则宏生成的函数将与原函数有相同的形参列表和 interactive 形式。同时，如果有一些使用 <code>evil-define-command</code> 定义的没法获取 interactive 命令的函数的话，会自动加上一个 interactive 命令。使用这个选项的宏在展开的时候必须确保原函数的定义已经完全加载， 否则会报错。</p>
<h1 id="使用"><a href="#使用" class="headerlink" title="使用"></a>使用<a id="sec-"></a></h1><h2 id="另一个宏"><a href="#另一个宏" class="headerlink" title="另一个宏"></a>另一个宏<a id="sec-"></a></h2><p>针对一些键绑定函数如 <code>define-key</code> 没法一次定义多个键绑定的问题，我定义了一个宏。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">(defmacro liu233w|bind-keys (binding-list &amp;rest func-and-args)</span><br><span class="line">  &quot;从列表自动生成多个键绑定命令</span><br><span class="line">语法为：</span><br><span class="line">\(liu233w|bind-keys ((\&quot;mn\&quot; &apos;func1) (\&quot;mp\&quot; &apos;func2))</span><br><span class="line">                       define-key evil-visual-state-map)</span><br><span class="line">键绑定会自动添加，不会自动调用 kbd。这个宏会生成多个键绑定函数的调用，</span><br><span class="line">每次都使用 binding-list 中的一项（去掉括号）放在函数调用的最后。</span><br><span class="line">除了 binding-list 以外，请使用和直接调用键绑定函数时相同的语法&quot;</span><br><span class="line">  `(progn</span><br><span class="line">     ,@(mapcar #&apos;(lambda (item)</span><br><span class="line">                   (append func-and-args item))</span><br><span class="line">               binding-list)))</span><br></pre></td></tr></table></figure>
<p>这样绑定按键可以方便一些，并且支持多种键绑定函数。</p>
<h2 id="示例-1"><a href="#示例-1" class="headerlink" title="示例 1"></a>示例 1<a id="sec-"></a></h2><p>绑定翻页键：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">;; 一个 micro-state，用来快速翻页</span><br><span class="line">(mms|define-multiple-micro-state</span><br><span class="line"> liu233w/view</span><br><span class="line"> :use-minibuffer t</span><br><span class="line"> :doc &quot;`d&apos; scroll-down `u&apos; scroll-up `f&apos; scroll-page-down `b&apos; scroll-page-up&quot;</span><br><span class="line"> :bindings</span><br><span class="line"> (&quot;d&quot; evil-scroll-down)</span><br><span class="line"> (&quot;u&quot; evil-scroll-up)</span><br><span class="line"> (&quot;f&quot; evil-scroll-page-down)</span><br><span class="line"> (&quot;b&quot; evil-scroll-page-up))</span><br><span class="line"></span><br><span class="line">(liu233w|bind-keys</span><br><span class="line"> (((kbd &quot;C-f&quot;) &apos;liu233w/view:evil-scroll-page-down-then-enter-micro-state)</span><br><span class="line">  ((kbd &quot;C-b&quot;) &apos;liu233w/view:evil-scroll-page-up-then-enter-micro-state)</span><br><span class="line">  ((kbd &quot;C-u&quot;) &apos;liu233w/view:evil-scroll-up-then-enter-micro-state)</span><br><span class="line">  ((kbd &quot;C-d&quot;) &apos;liu233w/view:evil-scroll-down-then-enter-micro-state))</span><br><span class="line"> evil-global-set-key &apos;normal)</span><br></pre></td></tr></table></figure>
<p>翻页之后会自动进入 micro-state。并且这种定义对于原来的函数（ <code>evil-scroll-page-up</code> ） 是没有影响的。</p>
<h2 id="示例-2"><a href="#示例-2" class="headerlink" title="示例 2"></a>示例 2<a id="sec-"></a></h2><p>定义 evil-mc 的按键：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">(mms|define-multiple-micro-state</span><br><span class="line"> liu233w/emc</span><br><span class="line"> :doc auto</span><br><span class="line"> :use-minibuffer t</span><br><span class="line"> :with-full-arguments t</span><br><span class="line"> :bindings</span><br><span class="line"> (&quot;n&quot; evil-mc-make-and-goto-next-match)</span><br><span class="line"> (&quot;j&quot; evil-mc-skip-and-goto-next-match)</span><br><span class="line"> (&quot;p&quot; evil-mc-make-and-goto-prev-match)</span><br><span class="line"> (&quot;k&quot; evil-mc-skip-and-goto-prev-match))</span><br><span class="line"></span><br><span class="line">(liu233w|bind-keys</span><br><span class="line"> (((kbd &quot;mn&quot;) #&apos;liu233w/emc:evil-mc-make-and-goto-next-match-then-enter-micro-state)</span><br><span class="line">  ((kbd &quot;mp&quot;) #&apos;liu233w/emc:evil-mc-make-and-goto-prev-match-then-enter-micro-state))</span><br><span class="line"> define-key evil-visual-state-map)</span><br></pre></td></tr></table></figure>
<p>生成的文档是这样的：</p>
<pre><code>`n&apos; evil-mc-make-and-goto-next-match  `j&apos; evil-mc-skip-and-goto-next-match  `p&apos; evil-mc-make-and-goto-prev-match  `k&apos; evil-mc-skip-and-goto-prev-match
</code></pre><h1 id="经验"><a href="#经验" class="headerlink" title="经验"></a>经验<a id="sec-"></a></h1><p>我在写这些代码的时候发现了一些好东西，在这里分享一下：</p>
<ol>
<li><code>defalias</code> 可以生成函数的别名，对这个别名使用 <code>advice-add</code> 的话只会把新函数 绑定到别名上，原来的函数不受影响。</li>
<li><code>interactive-form</code> 可以获得某个函数的 interactive 形式。</li>
<li><code>intern</code> 可以从一个字符串生成一个 symbol。</li>
<li><code>help-function-arglist</code> 可以获得某个函数的的参数列表。</li>
<li><code>command-execute</code> 可以像执行命令一样执行一个 interactive 函数，调用的函数不需要 保留参数信息， <code>command-execute</code> 会将要调用的函数需要的参数自行传递给它。</li>
</ol>

      </section>
      <div class="license">
        <a href="http://creativecommons.org/licenses/by-sa/3.0/" rel="license" target="_blank">
          <img src="http://i.creativecommons.org/l/by-sa/3.0/88x31.png" style="border-width:0"
               alt="Creative Commons License" class="center">
        </a>
      </div>
      <p>
        本作品采用
        <a rel="license" href="https://creativecommons.org/licenses/by-sa/4.0/deed.zh" target="_blank">
          署名-相同方式共享 4.0 国际
        </a>
        进行许可。欢迎转载、使用、重新发布，但务必保留文章署名
        “不科学的科学君” (Liu233w) 与博客链接：
        <a href="https://liu233w.github.io">https://liu233w.github.io</a>
        ，基于本文修改后的作品务必以相同的许可发布。如有任何疑问，请
        <a href="mailto:wwwlsmcom@outlook.com" target="_blank">与我联系</a>
        。
      </p>
    </article>
    
    <!-- disqus 评论框 start -->
    <div class="comment">
      <div id="disqus_thread" class="disqus-thread">
        <i>加载评论框需要翻墙</i>
      </div>
    </div>
    <!-- disqus 评论框 end -->
    
    
    <!-- livere 评论框 start -->
    <div class="comment">
      <div id="lv-container" data-id="city" data-uid="MTAyMC8zNTE5NS8xMTczMA=="></div>
    </div>
    <!-- livere 评论框 end -->
    
  </div>
  <aside>
    
    <div class="toc-container">
      <h1>目录</h1>
      <div class="content">
        <ol class="toc"><li class="toc-item toc-level-1"><a class="toc-link" href="#需求"><span class="toc-number">1.</span> <span class="toc-text">需求</span></a></li><li class="toc-item toc-level-1"><a class="toc-link" href="#我的实现"><span class="toc-number">2.</span> <span class="toc-text">我的实现</span></a></li><li class="toc-item toc-level-1"><a class="toc-link" href="#使用"><span class="toc-number">3.</span> <span class="toc-text">使用</span></a><ol class="toc-child"><li class="toc-item toc-level-2"><a class="toc-link" href="#另一个宏"><span class="toc-number">3.1.</span> <span class="toc-text">另一个宏</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#示例-1"><span class="toc-number">3.2.</span> <span class="toc-text">示例 1</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#示例-2"><span class="toc-number">3.3.</span> <span class="toc-text">示例 2</span></a></li></ol></li><li class="toc-item toc-level-1"><a class="toc-link" href="#经验"><span class="toc-number">4.</span> <span class="toc-text">经验</span></a></li></ol>
      </div>
    </div>
    
  </aside>
</main>

<!-- disqus 公共JS代码 -->
<script type="text/javascript">
  /* * * CONFIGURATION VARIABLES * * */
  var disqus_shortname = "liu233w";
  var disqus_identifier = "https://liu233w.github.io/2016/09/02/multiple-micro-state.org/";
  var disqus_url = "https://liu233w.github.io/2016/09/02/multiple-micro-state.org/";

  isAgent(getDisqus)

  // determine user agent in China
  function isAgent(cb) {
    var url = '//graph.facebook.com/feed?callback=h';
    var xhr = new XMLHttpRequest();
    var called = false;
    xhr.open('GET', url);
    xhr.onreadystatechange = function () {
      if (xhr.readyState === 4 && xhr.status === 200) {
        called = true;
        cb(true);
      }
    };
    xhr.send();
    // timeout 1s, this facebook API is very fast.
    setTimeout(function () {
      if (!called) {
        xhr.abort();
        cb(false)
      }
    }, 1000);
  }

  function getDisqus(isAgent) {
    var dsq = document.createElement('script');
    dsq.type = 'text/javascript';
    dsq.async = true
    dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js';
    (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq)
  }
</script>
<!-- disqus 公共JS代码 end -->


<script type="text/javascript">
  (function (d, s) {
    var j, e = d.getElementsByTagName(s)[0];

    if (typeof LivereTower === 'function') {
      return;
    }

    j = d.createElement(s);
    j.src = 'https://cdn-city.livere.com/js/embed.dist.js';
    j.async = true;

    e.parentNode.insertBefore(j, e);
  })(document, 'script');
</script>


  <footer>
  <div class="copyright">
    <div>
      &copy; 2019 | Powered by <a href="https://hexo.io" target="_blank">Hexo</a>&nbsp
    </div>
    <div>
      Theme by <a href="https://github.com/lewis-geek/hexo-theme-Aath" target="_blank">Aath</a>
    </div>
  </div>
</footer>


<script src="https://cdn.bootcss.com/jquery/3.2.1/jquery.min.js"></script>
<script src="/lib/in-view.min.js"></script>
<script src="/lib/lodash.min.js"></script>
<script>
  var isDown = true
  var oldY = 0
  inView.offset(50)

  document.body.addEventListener('touchstart', function(){});
  
  window.addEventListener('scroll', _.throttle(e => {
    var currentY = window.scrollY
    if((oldY - currentY) < 0) {
      isDown = true
    } else {
      isDown = false
    }
    oldY = currentY
  }, 250))

  $("article img").each(function() {
      var strA = "<a data-fancybox='gallery' href='" + this.src + "'></a>";
      $(this).wrapAll(strA);
  });

  $('.toc-link').each(function() {
      var href = $(this).attr("href");
      
      inView(href).on('exit', () => {
        if (isDown) {
          handleActive(href)
        }
      })

      inView(href).on('enter', () => {
        if (!isDown) {
          handleActive(href)
        }
      })

      this.onclick = function(e) {
        var pos = $(href).offset().top - 10;
        $("html,body").animate({scrollTop: pos}, 300);
        setTimeout(() => {
          handleActive(href)
        }, 350)
        return false
      }
  })

  function handleActive(href) {
    document.querySelectorAll('.toc-link').forEach(elm => {
      elm.classList.remove('active')
    })
    document.querySelector(".toc [href='"+ href +"']").classList.add('active')
  }
</script>
<script src="/lib/jquery.fancybox.min.js"></script>

<script async src="//dn-lbstatics.qbox.me/busuanzi/2.3/busuanzi.pure.mini.js"></script>

</body>
</html>
